package org.agile2soft.nexoma.tapestry.services;

import java.io.IOException;

import org.agile2soft.nexoma.controller.ServiceController;
import org.agile2soft.nexoma.controller.ServiceOptionController;
import org.agile2soft.nexoma.controller.ServicesGroupController;
import org.agile2soft.nexoma.entity.ExpedientTypePhase;
import org.agile2soft.nexoma.entity.ExpedientTypePhaseId;
import org.agile2soft.nexoma.entity.Service;
import org.agile2soft.nexoma.entity.ServiceOption;
import org.agile2soft.nexoma.entity.ServicesGroup;
import org.agile2soft.nexoma.entity.UserRequest;
import org.agile2soft.nexoma.entity.UserRequestId;
import org.agile2soft.nexoma.tapestry.model.ApplicationContainer;
import org.agile2soft.nexoma.tapestry.services.security.MyUserDetailsService;
import org.apache.tapestry5.*;
import org.apache.tapestry5.ioc.MappedConfiguration;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBinder;
import org.apache.tapestry5.ioc.annotations.EagerLoad;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.annotations.Local;
import org.apache.tapestry5.ioc.annotations.Scope;
import org.apache.tapestry5.ioc.services.AspectDecorator;
import org.apache.tapestry5.services.Request;
import org.apache.tapestry5.services.RequestFilter;
import org.apache.tapestry5.services.RequestHandler;
import org.apache.tapestry5.services.Response;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.AuthenticationProvider;
import org.springframework.security.providers.dao.SaltSource;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.userdetails.UserDetailsService;

import br.com.arsmachina.module.service.ControllerSource;
import br.com.arsmachina.module.service.PrimaryKeyTypeService;
import br.com.arsmachina.module.service.internal.HibernatePrimaryKeyTypeService;
import br.com.arsmachina.module.service.internal.JavassistMethodAdvice;
import br.com.arsmachina.tapestrycrud.factory.PrimaryKeyEncoderFactory;
import br.com.arsmachina.tapestrycrud.hibernate.factory.HibernatePrimaryKeyEncoderFactory;

/**
 * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to
 * configure and extend Tapestry, or to place your own service definitions.
 */
public class AppModule {


    public static void bind(ServiceBinder binder)
    {
        binder.bind(SupportedLocales.class,SupportedLocalesImpl.class);
    }


    /**
     * Contributes {@link HibernatePrimaryKeyTypeService} to the
     * {@link PrimaryKeyTypeService} service.
     *
     * It handles the entities with composite primary key
     *
     * @param configuration an {@link OrderedConfiguration} of
     *            {@link PrimaryKeyTypeService}'s.
     * @param sessionFactory a {@link SessionFactory}.
     */
//    public static void contributePrimaryKeyTypeService(
//            OrderedConfiguration<PrimaryKeyTypeService> configuration,
//            SessionFactory sessionFactory, AspectDecorator aspectDecorator,
//            JavassistMethodAdvice advice) {
//
//        PrimaryKeyTypeService service =
//            new HibernatePrimaryKeyTypeService(sessionFactory);
//
//        service =
//            aspectDecorator.build(PrimaryKeyTypeService.class, service, advice,
//                    "JavassistMethodAdvice");
//
//        configuration.add("hibernate", service, "before:default");
//
//    }

    /**
     * Contributes {@link HibernatePrimaryKeyEncoderFactory} to the
     * {@link PrimaryKeyEncoderFactory} service.
     *
     * @param configuration an {@link OrderedConfiguration} of
     *            {@link PrimaryKeyEncoderFactory}'s.
     * @param sessionFactory a {@link SessionFactory}.
     */
//    public static void contributePrimaryKeyEncoderFactory(
//            OrderedConfiguration<PrimaryKeyEncoderFactory> configuration,
//            SessionFactory sessionFactory, AspectDecorator aspectDecorator,
//            JavassistMethodAdvice advice, ControllerSource controllerSource,
//            PrimaryKeyTypeService primaryKeyTypeService) {
//
//        PrimaryKeyEncoderFactory service =
//            new HibernatePrimaryKeyEncoderFactory(sessionFactory, controllerSource, primaryKeyTypeService);
//
//        service =
//            aspectDecorator.build(PrimaryKeyEncoderFactory.class, service, advice,
//                    "JavassistMethodAdvice");
//
//        configuration.add("hibernate", service, "before:default");
//
//    }

    @Scope(value=ScopeConstants.PERTHREAD)
    public Authentication buildAuthentication () {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    /**
     * <b> This is not needed to use Tapestry CRUD: it's just an example of how to override some
     * default behaviours. </b> Contributes a specific {@link PrimaryKeyTypeService} to use a
     * {@link String} as the primary key for {@link Task}.
     *
     * @param configuration an {@link OrderedConfiguration} of {@link PrimaryKeyTypeService}'s.
     * @param sessionFactory a {@link SessionFactory}.
     */
    public static void contributePrimaryKeyTypeService(
            OrderedConfiguration<PrimaryKeyTypeService> configuration, SessionFactory sessionFactory) {

        PrimaryKeyTypeService primaryKeyTypeService = new PrimaryKeyTypeService() {

            @SuppressWarnings("unchecked")
            public String getPrimaryKeyPropertyName(Class entityClass) {
                return null;
            }

            @SuppressWarnings("unchecked")
            public Class getPrimaryKeyType(Class entityClass) {

                Class primaryKeyType = null;

                if (entityClass == UserRequest.class) {
                    primaryKeyType = UserRequestId.class;
                }
                else
                if (entityClass == ExpedientTypePhase.class ) {
                    primaryKeyType = ExpedientTypePhaseId.class;
                }
                return primaryKeyType;

            }

        };
        configuration.add("compositeKeys", primaryKeyTypeService, "before:*");
    }

    @EagerLoad
    public static ApplicationContainer buildApplicationContainer(final ServicesGroupController serviceGroupController,
                                                                 final ServiceController serviceController,
                                                                 final ServiceOptionController serviceOptionController) {
        if ( serviceGroupController.findAll().size()==0 ) {

            // MOCK DATA
            ServicesGroup portalGroup    = new ServicesGroup("Index","portal");
            ServicesGroup adminGroup     = new ServicesGroup("Management","admin");
            ServicesGroup expedientGroup = new ServicesGroup("Expedients","expedient");
            ServicesGroup documentGroup  = new ServicesGroup("Documents","document");

            serviceGroupController.save(portalGroup);
            serviceGroupController.save(adminGroup);
            serviceGroupController.save(expedientGroup);
            serviceGroupController.save(documentGroup);

            Service userAdminService     = new Service("Users","admin/Users","Users Management",true,false,"1",adminGroup);
            Service serviceAdminService  = new Service("Services","admin/Services","Services Management",true,false,"1",adminGroup);
            Service profilesAdminService = new Service("Profiles","admin/Profiles","Profiles Management",true,false,"1",adminGroup);

            Service expedientTypeService  = new Service("Types","expedient/Types","Expedient Type Management",true,false,"1",expedientGroup);
            Service expedientPhaseService = new Service("Phases","expedient/Phases","Expedient Phases Management",true,false,"1",expedientGroup);
            Service expedientService      = new Service("Expedients","expedient/List","Expedient Management",true,false,"1",expedientGroup);

            serviceController.save(userAdminService);
            serviceController.save(serviceAdminService);
            serviceController.save(profilesAdminService);

            serviceController.save(expedientTypeService);
            serviceController.save(expedientPhaseService);
            serviceController.save(expedientService);

            ServiceOption internalUsersListOption = new ServiceOption("Internal Users","admin/Users/InternalList",userAdminService);
            ServiceOption internalUsersEditOption = new ServiceOption("Create Internal User","admin/Users/InternalEdit",userAdminService);

            ServiceOption usersListOption = new ServiceOption("Users","admin/Users/List",userAdminService);
            ServiceOption usersEditOption = new ServiceOption("Create User","admin/Users/Edit",userAdminService);

            ServiceOption profileListOption = new ServiceOption("List Profiles","admin/Profiles/List",profilesAdminService);
            ServiceOption profileEditOption = new ServiceOption("Create Profile","admin/Profiles/Edit",profilesAdminService);

            ServiceOption servicesGroupListOption = new ServiceOption("List Groups","admin/Services/GroupList",serviceAdminService);
            ServiceOption servicesGroupEditOption = new ServiceOption("Create Group","admin/Services/GroupEdit",serviceAdminService);

            ServiceOption servicesListOption = new ServiceOption("List Services","admin/Services/List",serviceAdminService);
            ServiceOption servicesEditOption = new ServiceOption("Create Service","admin/Services/Edit",serviceAdminService);

            ServiceOption servicesOptionListOption = new ServiceOption("List Options","admin/Services/OptionList",serviceAdminService);
            ServiceOption servicesOptionEditOption = new ServiceOption("Create Option","admin/Services/OptionEdit",serviceAdminService);

            ServiceOption expedientTypeListOption = new ServiceOption("List Types","expedient/TypesList",expedientTypeService);
            ServiceOption expedientPhaseListOption = new ServiceOption("List Phases","expedient/PhasesList",expedientPhaseService);
            ServiceOption expedientListOption = new ServiceOption("List Expedients","expedient/List",expedientService);
            ServiceOption expedientOpenOption = new ServiceOption("Open Expedient","expedient/Open",expedientService);

            serviceOptionController.save(usersListOption);
            serviceOptionController.save(usersEditOption);

            serviceOptionController.save(internalUsersListOption);
            serviceOptionController.save(internalUsersEditOption);

            serviceOptionController.save(profileListOption);
            serviceOptionController.save(profileEditOption);

            serviceOptionController.save(servicesGroupListOption);
            serviceOptionController.save(servicesGroupEditOption);
            serviceOptionController.save(servicesListOption);
            serviceOptionController.save(servicesEditOption);
            serviceOptionController.save(servicesOptionListOption);
            serviceOptionController.save(servicesOptionEditOption);

            serviceOptionController.save(expedientTypeListOption);
            serviceOptionController.save(expedientPhaseListOption);
            serviceOptionController.save(expedientListOption);
            serviceOptionController.save(expedientOpenOption);

        }

        ApplicationContainer appContainer = new ApplicationContainer();
        appContainer.setServicesGroups(serviceGroupController.findAll());

        return appContainer;
    }


    public static UserDetailsService buildUserDetailsService(
            @Inject
            PasswordEncoder encoder,
            @Inject
            SaltSource salt ) {
        return new MyUserDetailsService( encoder, salt);
    }

    public static void contributeProviderManager( OrderedConfiguration<AuthenticationProvider> configuration
                                                 , @InjectService( "DaoAuthenticationProvider" )
                                                    AuthenticationProvider daoAuthenticationProvider ) {

        configuration.add( "daoAuthenticationProvider", daoAuthenticationProvider );
    }

    public static void contributeApplicationDefaults(
            MappedConfiguration<String, String> configuration)
    {
        configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,es,fr,de,jp");
        configuration.add(SymbolConstants.PRODUCTION_MODE, "false");
        configuration.add(SymbolConstants.APPLICATION_VERSION, "0.0.1-SNAPSHOT");
        // TAPESTRY-SPRING-SECURITY Configuration
        configuration.add( "tapestry.default-cookie-max-age", "31536000" );
        /* The internal check url used by Spring Security, Default to "/j_acegi_security_check". */
        configuration.add("spring-security.check.url","/j_acegi_security_check");
        /* Url redirected to when fails to login. Defaults to "/loginfailed". */
        configuration.add("spring-security.failure.url","/loginfailed");
        /* Url redirected to after a successful url ifr there is no "secured url" stored on session. Defaults to "/". */
        configuration.add("spring-security.target.url","/login");
        /* Url redirected to after a successful logout. Defaults to "/". */
        configuration.add("spring-security.afterlogout.url", "/logout");

        /*
         * Defaults to "". If set to other than empty, the request dispatcher will "forward" to this specified error page view. From Acegi
         * documentation: The error page to use. Must begin with a "/" and is interpreted relative to the current context root
        configuration.add("spring-security.accessDenied.url","/forbidden");
        /* Key used by the remember me services. Defauls to "REMEMBERMEKEY".*/
        configuration.add("spring-security.remenberme.key","REMENBERMEKEY");
        /* Url redirected to when trying to use a secured class and/or method. Defaults to "/login" */
        configuration.add("spring-security.loginform.url,", "/forbidden");
        /* Used to indicate if redirect to loginform should also switch to secure channel. */
        configuration.add("spring-security.force.ssl.login","false");
        /* Key used by the anonymous service. Defaults to "acegi_anonymous" */
        configuration.add( "spring-security.anonymous.key", "acegi_citizen" );
        /* Attributes set on anonymous uses. Defaults to "anonymous,ROLE_ANONYMOUS". */
        configuration.add( "spring-security.anonymous.attribute","citizen,ROLE_CITIZEN" );
        /* Salt the password is salted with. Defaults to "DEADBEEF" */
        configuration.add( "spring-security.password.salt", "DEADBEEF" );
    }

//    public static void contributeJavascriptStack(
//            @Path("context:resources/js/jquery.js") Asset jquery,
//            Configuration<Asset> stack){
//        stack.add(jquery);
//    }


    /**
     * This is a service definition, the service will be named "TimingFilter".
     */
    public RequestFilter buildTimingFilter(final Logger log)
    {
        return new RequestFilter()
        {
            public boolean service(Request request, Response response, RequestHandler handler)
                    throws IOException
            {
                long startTime = System.currentTimeMillis();

                try
                {
                    return handler.service(request, response);
                }
                finally
                {
                    long elapsed = System.currentTimeMillis() - startTime;

                    log.info(String.format("Request time: %d ms", elapsed));
                }
            }
        };
    }

    /**
     * This is a contribution to the RequestHandler service configuration. This is how we extend
     * Tapestry using the timing filter. A common use for this kind of filter is transaction
     * management or security. The @Local annotation selects the desired service by type, but only
     * from the same module.  Without @Local, there would be an error due to the other service(s)
     * that implement RequestFilter (defined in other modules).
     */
    public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
            @Local
            RequestFilter filter)
    {
        // Each contribution to an ordered configuration has a name, When necessary, you may
        // set constraints to precisely control the invocation order of the contributed filter
        // within the pipeline.

        configuration.add("Timing", filter);
    }

}
